/*
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "Glyph.h"
#include "Split.h"

#define deg2rad(d) (float)(M_PI/180*(d))

namespace ssf
{
Glyph::Glyph()
{
    c = 0;
    font = NULL;
    ascent = descent = width = spacing = fill = 0;
    tl.x = tl.y = tls.x = tls.y = 0;
}

float Glyph::GetBackgroundSize() const
{
    return style.background.size * (scale.cx + scale.cy) / 2;
}

float Glyph::GetShadowDepth() const
{
    return style.shadow.depth * (scale.cx + scale.cy) / 2;
}

CRect Glyph::GetClipRect() const
{
    CRect r = bbox + tl;

    int size = (int)(GetBackgroundSize() + 0.5);
    int depth = (int)(GetShadowDepth() + 0.5);

    r.InflateRect(size, size);
    r.InflateRect(depth, depth);

    r.left >>= 6;
    r.top >>= 6;
    r.right = (r.right + 32) >> 6;
    r.bottom = (r.bottom + 32) >> 6;

    return r;
}

void Glyph::CreateBkg()
{
    path_bkg.RemoveAll();

    if(style.background.type == L"enlarge" && style.background.size > 0)
    {
        path_bkg.Enlarge(path, GetBackgroundSize());
    }
    else if(style.background.type == L"box" && style.background.size >= 0)
    {
        if(c != ssf::Text::LSEP)
        {
            int s = (int)(GetBackgroundSize() + 0.5);
            int x0 = (!vertical ? -spacing / 2 : ascent - row_ascent);
            int y0 = (!vertical ? ascent - row_ascent : -spacing / 2);
            int x1 = x0 + (!vertical ? width + spacing : row_ascent + row_descent);
            int y1 = y0 + (!vertical ? row_ascent + row_descent : width + spacing);
            path_bkg.types.SetCount(4);
            path_bkg.types[0] = PT_MOVETO;
            path_bkg.types[1] = PT_LINETO;
            path_bkg.types[2] = PT_LINETO;
            path_bkg.types[3] = PT_LINETO;
            path_bkg.points.SetCount(4);
            path_bkg.points[0] = CPoint(x0 - s, y0 - s);
            path_bkg.points[1] = CPoint(x1 + s, y0 - s);
            path_bkg.points[2] = CPoint(x1 + s, y1 + s);
            path_bkg.points[3] = CPoint(x0 - s, y1 + s);
        }
    }
}

void Glyph::CreateSplineCoeffs(const CRect& spdrc)
{
    spline.RemoveAll();

    if(style.placement.path.IsEmpty())
        return;

    size_t i = 0, j = style.placement.path.GetCount();

    CAtlArray<Point> pts;
    pts.SetCount(j + 2);

    Point p;

    while(i < j)
    {
        p.x = style.placement.path[i].x * scale.cx + spdrc.left * 64;
        p.y = style.placement.path[i].y * scale.cy + spdrc.top * 64;
        pts[++i] = p;
    }

    if(pts.GetCount() >= 4)
    {
        if(pts[1].x == pts[j].x && pts[1].y == pts[j].y)
        {
            pts.SetAt(0, pts[j-1]);
            pts.SetAt(j + 1, pts[2]);
        }
        else
        {
            p.x = pts[1].x * 2 - pts[2].x;
            p.y = pts[1].y * 2 - pts[2].y;
            pts.SetAt(0, p);

            p.x = pts[j].x * 2 - pts[j-1].x;
            p.y = pts[j].y * 2 - pts[j-1].y;
            pts.SetAt(j + 1, p);
        }

        spline.SetCount(pts.GetCount() - 3);

        for(size_t i = 0, j = pts.GetCount() - 4; i <= j; i++)
        {
            static const float _1div6 = 1.0f / 6;

            SplineCoeffs sc;

            sc.cx[3] = _1div6 * (-  pts[i+0].x + 3 * pts[i+1].x - 3 * pts[i+2].x + pts[i+3].x);
            sc.cx[2] = _1div6 * (3 * pts[i+0].x - 6 * pts[i+1].x + 3 * pts[i+2].x);
            sc.cx[1] = _1div6 * (-3 * pts[i+0].x	            + 3 * pts[i+2].x);
            sc.cx[0] = _1div6 * (pts[i+0].x + 4 * pts[i+1].x + 1 * pts[i+2].x);

            sc.cy[3] = _1div6 * (-  pts[i+0].y + 3 * pts[i+1].y - 3 * pts[i+2].y + pts[i+3].y);
            sc.cy[2] = _1div6 * (3 * pts[i+0].y - 6 * pts[i+1].y + 3 * pts[i+2].y);
            sc.cy[1] = _1div6 * (-3 * pts[i+0].y                + 3 * pts[i+2].y);
            sc.cy[0] = _1div6 * (pts[i+0].y + 4 * pts[i+1].y + 1 * pts[i+2].y);

            spline.SetAt(i, sc);
        }
    }
}

void Glyph::Transform(GlyphPath& path, CPoint org, const CRect& subrect)
{
    // TODO: add sse code path

    float sx = style.font.scale.cx;
    float sy = style.font.scale.cy;

    bool brotate = style.placement.angle.x || style.placement.angle.y || style.placement.angle.z;
    bool bspline = !spline.IsEmpty();
    bool bscale = brotate || bspline || sx != 1 || sy != 1;

    float caz = cos(deg2rad(style.placement.angle.z));
    float saz = sin(deg2rad(style.placement.angle.z));
    float cax = cos(deg2rad(style.placement.angle.x));
    float sax = sin(deg2rad(style.placement.angle.x));
    float cay = cos(deg2rad(style.placement.angle.y));
    float say = sin(deg2rad(style.placement.angle.y));

    for(size_t i = 0, j = path.types.GetCount(); i < j; i++)
    {
        CPoint p = path.points[i];

        if(bscale)
        {
            float x, y, z, xx, yy, zz;

            x = sx * (p.x - org.x);
            y = sy * (p.y - org.y);
            z = 0;

            if(bspline)
            {
                float pos = vertical ? y + org.y + tl.y - subrect.top : x + org.x + tl.x - subrect.left;
                float size = vertical ? subrect.Size().cy : subrect.Size().cx;
                float dist = vertical ? x : y;

                const SplineCoeffs* sc;
                float t;

                if(pos >= size)
                {
                    sc = &spline[spline.GetCount() - 1];
                    t = 1;
                }
                else
                {
                    float u = size / spline.GetCount();
                    sc = &spline[max((int)(pos / u), 0)];
                    t = fmod(pos, u) / u;
                }

                float nx = sc->cx[1] + 2 * t * sc->cx[2] + 3 * t * t * sc->cx[3];
                float ny = sc->cy[1] + 2 * t * sc->cy[2] + 3 * t * t * sc->cy[3];
                float nl = 1.0f / sqrt(nx * nx + ny * ny);

                nx *= nl;
                ny *= nl;

                x = sc->cx[0] + t * (sc->cx[1] + t * (sc->cx[2] + t * sc->cx[3])) - ny * dist - org.x - tl.x;
                y = sc->cy[0] + t * (sc->cy[1] + t * (sc->cy[2] + t * sc->cy[3])) + nx * dist - org.y - tl.y;
            }

            if(brotate)
            {
                xx = x * caz + y * saz;
                yy = -(x * saz - y * caz);
                zz = z;

                x = xx;
                y = yy * cax + zz * sax;
                z = yy * sax - zz * cax;

                xx = x * cay + z * say;
                yy = y;
                zz = x * say - z * cay;

                zz = 1.0f / (max(zz, -19000) + 20000);

                x = (xx * 20000) * zz;
                y = (yy * 20000) * zz;
            }

            p.x = (int)(x + org.x + 0.5);
            p.y = (int)(y + org.y + 0.5);

            path.points[i] = p;
        }

        if(p.x < bbox.left) bbox.left = p.x;
        if(p.x > bbox.right) bbox.right = p.x;
        if(p.y < bbox.top) bbox.top = p.y;
        if(p.y > bbox.bottom) bbox.bottom = p.y;
    }
}

void Glyph::Transform(CPoint org, const CRect& subrect)
{
    if(!style.placement.org.auto_x) org.x = style.placement.org.x * scale.cx;
    if(!style.placement.org.auto_y) org.y = style.placement.org.y * scale.cy;

    org -= tl;

    bbox.SetRect(INT_MAX, INT_MAX, INT_MIN, INT_MIN);

    Transform(path_bkg, org, subrect);
    Transform(path, org, subrect);

    bbox |= CRect(0, 0, 0, 0);
}

void Glyph::Rasterize()
{
    if(!path_bkg.IsEmpty())
    {
        ras_bkg.ScanConvert(path_bkg, bbox);
        ras_bkg.Rasterize(tl.x, tl.y);
    }

    ras.ScanConvert(path, bbox);

    if(style.background.type == L"outline" && style.background.size > 0)
    {
        ras.CreateWidenedRegion((int)(GetBackgroundSize() + 0.5));
    }

    //

    Rasterizer* r = path_bkg.IsEmpty() ? &ras : &ras_bkg;
    int plane = path_bkg.IsEmpty() ? (style.font.color.a < 255 ? 2 : 1) : 0;

    ras.Rasterize(tl.x, tl.y);
    r->Blur(style.background.blur, plane);

    if(style.shadow.depth > 0)
    {
        ras_shadow.Reuse(*r);

        float depth = GetShadowDepth();

        tls.x = tl.x + (int)(depth * cos(deg2rad(style.shadow.angle)) + 0.5);
        tls.y = tl.y + (int)(depth * -sin(deg2rad(style.shadow.angle)) + 0.5);

        ras_shadow.Rasterize(tls.x, tls.y);
        ras_shadow.Blur(style.shadow.blur, plane ? 1 : 0);
    }
}

}